package org.rcgwt.client.ui.grid.reflect;

import java.util.ArrayList;
import java.util.List;

import org.rcgwt.client.model.BeanProperty;
import org.rcgwt.client.model.IsReflected;
import org.rcgwt.client.model.Reflected;
import org.rcgwt.client.model.ReflectedFactory;
import org.rcgwt.client.ui.grid.event.IModelChangeState.ChangeStateType;
import org.rcgwt.client.ui.grid.internal.ModelChangeState;
import org.rcgwt.client.ui.grid.model.AbstractDataModel;
import org.rcgwt.client.ui.grid.model.BasicColumn;
import org.rcgwt.client.ui.grid.provider.BasicColumnProvider;
import org.rcgwt.client.ui.grid.provider.IColumnProvider;
import org.rcgwt.generators.ReflectedGenerator;

import com.google.gwt.event.logical.shared.ValueChangeEvent;

/**
 * Transform {@link IsReflected} to the {@link Reflected} Gwt compiler call
 * {@link ReflectedGenerator} for that
 * 
 * @author Matthieu ROBERT & Olivier Oeuillot
 * 
 * @param <T>
 */
public class ReflectedDataModel<T> extends AbstractDataModel implements
        IReflectedDataModel<T> {

    private final ArrayList<Reflected<T>> records = new ArrayList<Reflected<T>>();

    protected final BeanProperty beanProperties[];

    public ReflectedDataModel(Class<T> modelClass) {

        beanProperties = ReflectedFactory.getInstance().getBeanProperties(
                modelClass.getName());

        if (beanProperties == null) {
            throw new IllegalStateException("Class '" + modelClass
                    + "' is not supported ! (add @"
                    + IsReflected.class.getName() + " annotation)");
        }
    }

    @Override
    public int getRowCount() {
        return records.size();
    }

    @Override
    public Object getValueAt(int rowModelIndex, String columnName) {
        Reflected<T> object = getRowValueAt(rowModelIndex);

        return object.get(columnName);
    }

    @Override
    public boolean isRowAvailable(int rowIndex) {
        return records.size() > rowIndex;
    }

    @Override
    public Reflected<T> getRowValueAt(int rowModelIndex) {

        if (rowModelIndex < 0 || rowModelIndex >= records.size()) {
            throw new IndexOutOfBoundsException("Index=" + rowModelIndex);
        }

        Reflected<T> reflected = records.get(rowModelIndex);

        return reflected;
    }

    @Override
    public void processRowInserted(int start, List<T> rows) {

        if (rows == null) {
            return;
        }

        int rowsSize = rows.size();
        if (rowsSize < 1) {
            return;
        }

        List<Reflected<T>> added = new ArrayList<Reflected<T>>(rowsSize);

        for (int i = 0; i < rowsSize; i++) {
            T value = rows.get(i);
            Reflected<T> ref = null;

            if (value != null) {
                ref = createReflected(value);
            }

            added.add(ref);
        }

        records.ensureCapacity(start + rowsSize);
        records.addAll(start, added);

        ValueChangeEvent.fire(this, new ModelChangeState(start, 0, rowsSize,
                ChangeStateType.STRUCTURE));

    }

    @Override
    public void processRowRemoved(int start, int count) {

        if (count < 0 || start + count > records.size()) {
            count = records.size() - start;
        }

        if (count < 1) {
            return;
        }

        if (start == 0 && count == records.size()) {
            records.clear();

        } else {
            removeListItems(records, start, count);
        }

        ValueChangeEvent.fire(this, new ModelChangeState(start, count, -count,
                ChangeStateType.STRUCTURE));

    }

    @Override
    public void processRowUpdated(int start, int count) {

        if (count < 0 || start + count > records.size()) {
            count = records.size() - start;
        }

        if (count < 1) {
            return;
        }

        ValueChangeEvent.fire(this, new ModelChangeState(start, count, 0,
                ChangeStateType.UPDATE));

    }

    @Override
    public void processRowReplaced(int start, int count, List<T> rows) {
        assert rows != null : "Rows is null";

        if (count < 0 || start + count > records.size()) {
            count = records.size() - start;

            if (count < 0) {
                count = 0;
            }
        }

        boolean delete = false;

        if (count > 0) {
            delete = true;

            if (start == 0 && count == records.size()) {
                records.clear();

            } else {
                removeListItems(records, start, count);
            }
        }

        int rowsSize = (rows != null) ? rows.size() : 0;
        if (rowsSize > 0) {
            records.ensureCapacity(start + rowsSize);

            List<Reflected<T>> added = new ArrayList<Reflected<T>>();

            for (int i = 0; i < rowsSize; i++) {
                T value = rows.get(i);
                Reflected<T> ref = null;

                if (value != null) {
                    ref = createReflected(value);
                }

                added.add(ref);

            }

            records.addAll(start, added);

            ValueChangeEvent.fire(this, new ModelChangeState(start, count,
                    added.size() - count, ChangeStateType.STRUCTURE));

        } else if (delete) {
            ValueChangeEvent.fire(this, new ModelChangeState(start, count,
                    -count, ChangeStateType.STRUCTURE));

        }
    }

    private static void removeListItems(List< ? > l, int start, int length) {

        for (int i = 0; i < length; i++) {
            l.remove(start);
        }
    }

    private static <T> Reflected<T> createReflected(T value) {
        return ReflectedFactory.getInstance().createReflected(value);
    }

    @Override
    public IColumnProvider getColumnProvider() {

        BasicColumnProvider columnProvider = new BasicColumnProvider();

        for (BeanProperty beanProperty : beanProperties) {
            BasicColumn column = new BasicColumn(beanProperty.getName(),
                    beanProperty.getName());
            column.setResizable(true);
            column.setSortable(true);

            String type = beanProperty.getType();

            if ("long".equals(type)) {
                column.setWidth(30);
                column.setMinWidth(20);
                column.setMaxWidth(40);

            } else {
                column.setWidth(150);
            }

            columnProvider.addColumn(column);
        }

        return columnProvider;
    }
}
